package cn.jaye.fabric.domain.service;

import cn.jaye.fabric.common.constants.FarbicConstant;
import cn.jaye.fabric.common.constants.Global;
import cn.jaye.fabric.common.constants.exception.OrgExceptionEnum;
import cn.jaye.fabric.common.constants.exception.UserExceptionEnum;
import cn.jaye.fabric.common.constants.mapping.Table;
import cn.jaye.fabric.common.model.ServiceException;
import cn.jaye.fabric.domain.model.Org;
import cn.jaye.fabric.domain.model.SampleStore;
import cn.jaye.fabric.domain.model.SampleUser;
import cn.jaye.fabric.domain.repository.OrgRepository;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import org.hyperledger.fabric.sdk.User;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric_ca.sdk.HFCAClient;
import org.hyperledger.fabric_ca.sdk.RegistrationRequest;
import org.hyperledger.fabric_ca.sdk.exception.EnrollmentException;
import org.hyperledger.fabric_ca.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric_ca.sdk.exception.RegistrationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.Map;

import static java.lang.String.format;

/**
 * @author Jaye
 * @create 2018-05-08 13:58
 */
@Service
public class UserService {
    @Autowired
    private OrgRepository orgRepository;

    @Autowired
    private ConfigService configService;

    @Autowired
    private FarbicConstant farbicConstant;

    private SampleStore sampleStore;

    private Map<String, User> userMap = new HashMap<>();

    @PostConstruct
    private void init() {
        File sampleStoreFile = new File(System.getProperty("java.io.tmpdir") + "/HFCSampletest.properties");
        if (sampleStoreFile.exists()) {
            sampleStoreFile.delete();
        }
        this.sampleStore = new SampleStore(sampleStoreFile);
    }

    public String register(String username, String orgId) throws Exception {
        Org org = orgRepository.selectOne(new EntityWrapper<Org>().eq(Table.Org.orgId.getFieldValue(), orgId));
        if (org != null) {
            HFCAClient ca = HFCAClient.createNewInstance(org.getCaLocation(), configService.createCaProperties(org.getTlsFlag(), org.getDomainName()));
            ca.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
            SampleUser admin = sampleStore.getMember(farbicConstant.getAdminUsername(), orgId);
            if (!admin.isEnrolled()) {
                admin.setEnrollment(ca.enroll(farbicConstant.getAdminUsername(), farbicConstant.getAdminPassword()));
                admin.setMspId(org.getMspId());
            }
            RegistrationRequest registrationRequest = new RegistrationRequest(username, org.getOrgName() + Global.USER_AFFILIATION);
//            SampleUser peerOrgAdmin=getSampleAdmin(org.getOrgName(), org.getMspId(), org.getDomainName());
            try {
                String userpassword = ca.register(registrationRequest, admin);
                return userpassword;
            } catch (Exception e) {
                throw new ServiceException(UserExceptionEnum.USER_REGISTER);
            }
        } else {
            throw new ServiceException(OrgExceptionEnum.NO_DATA);
        }
    }

    public boolean login(String username, String password, String orgId) throws Exception {
        Org org = orgRepository.selectOne(new EntityWrapper<Org>().eq(Table.Org.orgId.getFieldValue(), orgId));
        if (org != null) {
            HFCAClient ca = HFCAClient.createNewInstance(org.getCaLocation(), configService.createCaProperties(org.getTlsFlag(), org.getDomainName()));
            ca.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
            SampleUser user = sampleStore.getMember(username, orgId);
            try {
                user.setEnrollment(ca.enroll(username, password));
                user.setMspId(org.getMspId());
                userMap.put(username + password, user);
                return true;
            } catch (EnrollmentException e) {
                throw new ServiceException(UserExceptionEnum.USER_PASSWORD_ERROR);
            }
        } else {
            throw new ServiceException(OrgExceptionEnum.NO_DATA);
        }
    }

    public SampleUser getSampleAdmin(String orgName, String mspId, String domainName) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException, IOException {
        String orgDomainName = orgName + "." + domainName;
        File privateKeyFile = findFileSk(Paths.get(farbicConstant.getKeyStoreLocation(), "crypto-config/peerOrganizations/",
                orgDomainName, format("/users/Admin@%s/msp/keystore", orgDomainName)).toFile());
        File certificateFile = Paths.get(farbicConstant.getKeyStoreLocation(), "crypto-config/peerOrganizations/", orgDomainName,
                format("/users/Admin@%s/msp/signcerts/Admin@%s-cert.pem", orgDomainName, orgDomainName)).toFile();
        SampleUser peerOrgAdmin = sampleStore.getMember(orgName + "Admin", orgName, mspId, privateKeyFile, certificateFile);
        return peerOrgAdmin;
    }

    public SampleUser getSampleAdmin(Org org) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, IOException {
        return getSampleAdmin(org.getOrgName(), org.getMspId(), org.getDomainName());
    }

    public User getUser(cn.jaye.fabric.domain.model.User user) {
        User sdkUser = userMap.get(user.getUsername() + user.getPassword());
        if (sdkUser == null) {
            try {
                login(user.getUsername(), user.getPassword(), user.getOrgId());
                return userMap.get(user.getUsername() + user.getPassword());
            } catch (Exception e) {
                throw new ServiceException(UserExceptionEnum.USER_PASSWORD_ERROR);
            }
        }
        return sdkUser;
    }

    public SampleUser getSampleAdmin(String orgId) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, IOException {
        Org org = orgRepository.selectOne(new EntityWrapper<Org>().eq(Table.Org.orgId.getFieldValue(), orgId));
        if (org != null) {
            return getSampleAdmin(org.getOrgName(), org.getMspId(), org.getDomainName());
        }
        throw new ServiceException(OrgExceptionEnum.NO_DATA);
    }

    public boolean isAdmin(String username) {
        return farbicConstant.getAdminUsername().equals(username);
    }

    private File findFileSk(File directory) {

        File[] matches = directory.listFiles((dir, name) -> name.endsWith("_sk"));

        if (null == matches) {
            throw new RuntimeException(format("Matches returned null does %s directory exist?", directory.getAbsoluteFile().getName()));
        }

        if (matches.length != 1) {
            throw new RuntimeException(format("Expected in %s only 1 sk file but found %d", directory.getAbsoluteFile().getName(), matches.length));
        }

        return matches[0];

    }
}
